-module(edrp_load).

-export([rrobin/2,
         random/2,
         lload/2,
         select2/2,
         weight/2,
         cache/2]).

-include("edrp_server.hrl").

%%==========================================================================
%% Load functions
%%==========================================================================

rrobin(#loads{serverList=[Server|_]}, _) ->
    UpdateLoadsFunc = 
    fun(Loads = #loads{serverList=[H|T]}) ->
        NewList = update_server(H, (T++[H])),
        Loads#loads{serverList=NewList}
    end,
    {Server#server.addr, UpdateLoadsFunc}.

random(#loads{serverList=List, length=Len}, _) ->
    Server = lists:nth(random:uniform(Len), List),
    UpdateLoadsFunc = 
    fun(Loads = #loads{serverList=L}) ->
        NewList = update_server(Server, L),
        Loads#loads{serverList=NewList}
    end,
    {Server#server.addr, UpdateLoadsFunc}.

lload(#loads{serverList=[H|T]}, _) ->
    FindLeastLoadFunc = 
    fun(Server, {LeastLoad, LeastLoadServer}) ->
        if
            Server#server.load < LeastLoad ->
                {Server#server.load, Server};
            true ->
                {LeastLoad, LeastLoadServer}
        end
    end,
    {_, Server} = lists:foldl(FindLeastLoadFunc, {H#server.load, H}, T),

    UpdateLoadsFunc = 
    fun(Loads = #loads{serverList=L}) ->
        NewList = update_server(Server, L),
        Loads#loads{serverList=NewList}
    end,
    {Server#server.addr, UpdateLoadsFunc}.

select2(#loads{serverList=List, length=Len}, _) ->
    Server1 = lists:nth(random:uniform(Len), List),
    Server2 = lists:nth(random:uniform(Len), List),
    Server = if
                Server1#server.load < Server2#server.load -> Server1;
                true -> Server2
             end,
    
    UpdateLoadsFunc = 
    fun(Loads = #loads{serverList=L}) ->
        NewList = update_server(Server, L),
        Loads#loads{serverList=NewList}
    end,
    {Server#server.addr, UpdateLoadsFunc}.

weight(#loads{serverList=List}, _) ->
    F = fun(Server, {Index, Len, IndexList}) ->
            NewIndexList = 
            insert_index(IndexList, Index, Server#server.weight),
            {Index+1, Len+Server#server.weight, NewIndexList}
        end,
    {_, Length, IndexList} = lists:foldl(F, {1, 0, []}, List),
    Index = lists:nth(random:uniform(Length), IndexList),
    Server = lists:nth(Index, List),
    
    UpdateLoadsFunc = 
    fun(Loads = #loads{serverList=L}) ->
        NewList = update_server(Server, L),
        Loads#loads{serverList=NewList}
    end,
    {Server#server.addr, UpdateLoadsFunc}.

cache(Loads = #loads{reqHash=ReqHash}, Request) ->
    Key = erlang:phash2(Request, ?NHASH) + 1,
    case lists:keysearch(Key, 1, ReqHash) of
        false ->    
            {Reply,_} = lload(Loads, Request);
        {value, {Key, Reply}} -> 
            Reply
    end,
    
    UpdateLoadsFunc = 
    fun(L = #loads{serverList=List, reqHash=Hash}) ->
        Server = lists:keysearch(Reply, 2, List),
        NewList = update_server(Server, List),
        NewHash = lists:keystore(Key, 1, Hash, {Key, Reply}),
        L#loads{serverList=NewList, reqHash=NewHash}
    end,
    {Reply, UpdateLoadsFunc}.
     
%%==========================================================================
%% Internal functions
%%==========================================================================

update_server(Server = #server{addr=Addr, load=Load}, ServerList) ->
    NewServer = Server#server{load=Load+1},
    lists:keyreplace(Addr, 2, ServerList, NewServer).

insert_index(IndexList, _Index, 0) -> IndexList;
insert_index(IndexList, Index, Count) -> 
    insert_index([Index|IndexList], Index, Count-1).
